﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.IO;

using log4net;

namespace MND.Telnet
{
    class Connection : IMessageSource
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(Connection));

        public event EventHandler<EventArgs> ConnectionClosed;

        public static ArrayList List = new ArrayList();

        Socket socket;
        byte[] buffer;

        private Queue<String> Output { get; set; }
        private StringBuilder sb = new StringBuilder();
        
        public Connection(Socket s)
        {
            socket = s;
            buffer = new byte[1024];
            Output = new Queue<string>();
            lock (List)
            {
                List.Add(this);
            }

            BeginReceive();
        }

        public string GetNextMessage()
        {
            lock (Output)
            {
                if (Output.Count > 0)
                    return Output.Dequeue();
                else
                    return null;
            }
        }

        public void BeginReceive()
        {
            socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), this);
        }



        private void ReceiveCallback(IAsyncResult result)
        {
            int bytesRead = socket.EndReceive(result);
            if (bytesRead > 0)
            {
                byte[] o = new byte[bytesRead];
                Array.Copy(buffer, o, bytesRead);
                //log.Debug("Read bytes:" + BitConverter.ToString(o));
                try
                {
                    Translate(o);
                }
                catch (Exception e)
                {
                    log.Debug("Error: ",e); 
                }
                BeginReceive();
            }
            else
            {
                Close();
            }
        }

        private string Translate(byte[] data)
        {
            return Translate(data, new ASCIIEncoding());
        }

        private string Translate(byte[] data, Encoding encoding)
        {
            #region TelnetOperations
            TelnetStates state = TelnetStates.NORMAL;
            int? operation = null;
            int? option = null;
            List<int> negotiationParams = new List<int>();
 
            foreach (byte b in data)
            {
                char c = Convert.ToChar(b);
                //log.Debug("Current character: " + ((int)c).ToString());
                switch (state)
                {
                    case TelnetStates.NORMAL:
                        switch ((int)c)
                        {
                            case TelnetCodes.IAC:
                                state = TelnetStates.IAC;
                                break;

                            case TelnetCodes.LF:
                                //if (sb.Length > 0)
                                {
                                    PushLine(sb.ToString());
                                    sb.Clear();
                                }
                                break;
                            case TelnetCodes.CR:
                                if (sb.Length > 0)
                                {
                                    PushLine(sb.ToString());
                                    sb.Clear();
                                }
                                break;
                            default:
                                sb.Append(c);
                                break;
                        }
                        break;

                    case TelnetStates.IAC:
                        switch ((int)c)
                        {
                            case TelnetCodes.WILL:
                            case TelnetCodes.WONT:
                            case TelnetCodes.DO:
                            case TelnetCodes.DONT:
                                operation = (int)c;
                                state = TelnetStates.OPERATION;
                                break;
                            case TelnetCodes.SB:
                                state = TelnetStates.NEGOTIATION;
                                break;
                            default:
                                // Unsupported code, revert to normal.
                                // Observe actual output to see, if needs more processing here.
                                state = TelnetStates.NORMAL;
                                break;
                        }
                        break;

                    case TelnetStates.OPERATION:
                        option = (int)c;
                        PerformTelnetOperation(operation, option, null);
                        
                        // Revert
                        option = null;
                        operation = null;
                        state = TelnetStates.NORMAL;
                        break;

                    case TelnetStates.NEGOTIATION:
                        switch ((int)c)
                        {
                            case TelnetCodes.SE:
                                PerformTelnetOperation(operation, option, negotiationParams.ToArray());

                                // Revert
                                state = TelnetStates.NORMAL;
                                option = null;
                                operation = null;
                                negotiationParams.Clear();
                                break;

                            default:
                                if (option == null)
                                    option = (int)c;
                                else
                                    negotiationParams.Add((int)c);
                                break;
                        }
                        break;
                }
            }
            #endregion

            return sb.ToString();
        }

        private void PushLine(string p)
        {
            lock (Output)
            {
                Output.Enqueue(p);
            }
        }

        private void PerformTelnetOperation(int? operation, int? option, int[] negotiations)
        {
            // Ignore for now
            //throw new NotImplementedException();
        }
        internal enum TelnetStates
        {
            NORMAL,
            IAC,
            OPERATION,
            NEGOTIATION,
        }


        public void Send(string s, Encoding e)
        {
            SocketAsyncEventArgs sae = new SocketAsyncEventArgs();
            sae.SetBuffer(e.GetBytes(s), 0, e.GetByteCount(s));
            socket.SendAsync(sae);
        }

        public void Close()
        {
            socket.Close();
            lock (List)
            {
                List.Remove(this);
            }
            EventHandler<EventArgs> cClosed = ConnectionClosed;
            if (cClosed != null)
                cClosed(this, EventArgs.Empty);
        }
    }
}
